事件循环是一个循环,只要 Node.js 应用程序正在运行,它就会一直运行。每个循环中有六个不同的队列,每个队列都包含一个或多个需要在调用堆栈上执行的回调函数。

下面这个配图展示了常见的异步回调方法。

看到上面的配图是不是有一点点蒙?不知道这些回调的执行顺序是怎样的?

当我们了解完 Node.js 的事件循环 (Event Loop) 规则之后就能够知道。

1 node 中的 Event Loop

Node 与浏览器中的 Event Loop 完全不同,

Node 的 Event Loop 分为 6 个阶段,会按照顺序反复执行,每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。

图片

2 阶段介绍

2.1 6 个阶段

2.2 2 个微任务队列

在 6 个阶段中并没有显示 process.nextTick,虽然它也是异步的,因为从实现上来说他不是事件循环的一部分。在某一操作完成后,将处理 nextTickQueue,而不考虑事件循环的当前阶段。

在给定阶段调用 process.nextTick() 时,在 process.nextTick() 触发的所有回调执行之前,事件循环将会停顿。

这可能会带来一些极端的情况,通过递归调用 process.nextTick() 而使 I/O 停止,可以阻止事件循环进入轮循阶段。

我们通过下面的例子先来简单看看结果。

2.3 简单输出示例

下面是个较完整的包含相关阶段执行的例子,

```js import fs from 'fs'

process.on('exit', () => { console.log('11') })

// 异步读取文件 fs.readFile('./1.mjs', () => { console.log('10') })

setTimeout(() => { console.log('8') }, 0) setTimeout(() => { console.log('9') }, 0)

process.nextTick(() => { console.log('2') Promise.resolve().then(() => { console.log('5') }) process.nextTick(() => { console.log('3') process.nextTick(() => { console.log('4') }) }) })

setImmediate(() => { console.log('6') Promise.resolve().then(() => { console.log('7') }) })

console.log('1') ```

下面展开介绍一下执行规则。

3 执行规则总结

毋庸置疑,先执行同步代码,紧接着才是回调部分。

  1. 执行微任务队列中所有的回调:首先执行 nextTick队列,然后 promise队列

```js Promise.resolve().then(() => { console.log(4) })

process.nextTick(() => { console.log(2) Promise.resolve().then(() => { console.log(5) }) process.nextTick(() => { console.log(3) }) })

console.log(1) ```

  1. 执行计时器队列中的所有回调,执行过程中如果存在微任务队列,则在每个回调之后就执行微任务队列中的回调

```js setTimeout(() => { Promise.resolve().then(() => { console.log(4) }) process.nextTick(() => { console.log(3) }) console.log(2) }, 0)

setTimeout(() => { console.log(5) }, 0)

console.log(1) ```

  1. 执行 I/O 队列中的所有回调,如果过程中存在微任务队列,则先执行微任务。

```js const fs = require('fs')

fs.readFile(__filename, () => { console.log(3) Promise.resolve().then(() => { console.log(3) }) })

fs.readFile(__filename, () => { console.log(4) Promise.resolve().then(() => { console.log(4) }) })

setTimeout(() => { console.log(2) }, 0)

console.log(1)

// 输出结果有2种,取决于哪个异步IO事件先被系统处理完 // 124433 // 123344 ```

  1. 执行 setImmediate 回调,同理有微任务,优先执行微任务。

```js setImmediate(() => { Promise.resolve().then(() => { console.log(4) }) process.nextTick(() => { console.log(3) }) console.log(2) })

setImmediate(() => { console.log(5) }) console.log(1) ```

  1. 执行关闭队列中的所有回调,最后执行剩余的微任务。

```js process.on('exit', () => { Promise.resolve().then(() => { console.log(4) }) console.log(2) })

process.on('exit', () => { Promise.resolve().then(() => { console.log(5) }) console.log(3) })

console.log(1) ```

小结

本节主要介绍了 Node.js 中事件循环的工作原理,以及各个阶段的执行顺序和执行规则。

这个是很有必要掌握的知识,因为它是 Node.js 异步编程的基础,也是理解 Node.js 的关键。

掌握其原理,从而更从容的使用 Node.js。

推荐阅读

如果笔者的介绍还是不能让你清晰的理解,可以进一步阅读下面的推荐材料。

文中部分配图来自于下面的参考文章